package video;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;
import javax.microedition.media.PlayerListener;
import javax.microedition.media.control.VideoControl;



public class MIDPPixelSource implements VideoListener, Runnable, PlayerListener {
	Player p;

	VideoControl vc;

	int imageWidth;

	int imageHeight;

	byte[] compressedImageBytes; // compressed image as bytes

	int[] uncompressedImageArray; // uncompressed image as bytes

	VideoListener listener;

	int interval = 66;

	Thread tickler;

	boolean fakeVideo;

	byte[] fakeVideoPngArray;

	Image fakeVideoImage;

	Graphics fakeVideoGraphics;

	int xDir = 1;

	int yDir = 1;
	
	int zDir = 1;
	
	int z = 30;

	int x = 0;

	int y = 0;

	String encoding = "png";
	
	Canvas displayCanvas;
	
	Canvas updateCanvas;

	/**
	 * Creates a connect to the camera for getting images and pixels. If video capture is not available, a fake video screen with a bouncing ball will appear.
	 * 
	 * @param _w
	 *            width the image
	 * @param _h
	 *            height of the image
	 */
	public MIDPPixelSource(int _w, int _h) {
		imageWidth = _w;
		imageHeight = _h;
		startVideo("capture://video", null);

	}

	/**
	 * Creates a connect to the camera for getting images and pixels. This will not even check for the availability of video capture but will go directly to the fake video.
	 * 
	 * @param _w
	 *            width the image
	 * @param _h
	 *            height of the image
	 */
	public MIDPPixelSource(int _w, int _h, boolean _fakeVideo) {
		// http://developers.sun.com/techtopics/mobility/midp/articles/mmapioverview/
		imageWidth = _w;
		imageHeight = _h;
		fakeIt();
		// myImage = Image.createImage(fakeVideoPngArray,0, fakeVideoPngArray.length);
	}

	/**
	 * Creates a connect to a video for getting images and pixels.
	 * 
	 * @param _w
	 *            width the image
	 * @param _h
	 *            height of the image
	 * @param _url
	 *            the address of the video. could be "capture://video" for the camera
	 *            or http://bla bal for a video.
	 * @param _canvas 
	 * 			 for faster rendering to the screen you can supply a canvas that will
	 * 				automatically get video put on it.  I don't think you get much control after that.
	 *
	 * 			
	 */
	public MIDPPixelSource(int _w, int _h, String _url, Canvas _canvas) {
		// http://developers.sun.com/techtopics/mobility/midp/articles/mmapioverview/
		imageWidth = _w;
		imageHeight = _h;
		displayCanvas = _canvas;
		startVideo(_url, _canvas);
		setDimensions(_w,_h);
	
	}
	
	public void fakeIt() {
		fakeVideo = true;
		System.out.println("Fake The Video");
		//try {
		
		InputStream is = this.getClass().getResourceAsStream("/fakeVideo.png");
		
	
		try{
			fakeVideoPngArray = new byte[is.available()];
			is.read(fakeVideoPngArray);
				} catch (Exception e) {
			System.out.println("Problem finding fakeVideo.png put a 160 x 120 .png file named \"fakeVideo.png\"  in your resource directory or in the root of your project directory");

		}

		fakeVideoImage = Image.createImage(imageWidth, imageHeight);
		fakeVideoGraphics = fakeVideoImage.getGraphics();
		
		addVideoListener(this, 30);

	}



	
	void startVideo(String _url, Canvas _canvas)  {
		
		if (_url == null) _url = "capture://video";
		System.out.println("Looking for: " + _url);
		boolean isCaptureURL = _url.indexOf("capture:") != -1;
	
		String canCapture = System.getProperty("supports.video.capture");


		if (isCaptureURL && canCapture != null && canCapture.toLowerCase().indexOf("false") != -1) {
			fakeIt();
		} else {
			try {
				p = Manager.createPlayer(_url);
		
				p.prefetch();
				p.realize();

				p.addPlayerListener(this);
				//if (isCaptureURL) 
					//p.start();
				
				vc = (VideoControl) p.getControl("VideoControl");
				if (_canvas != null) {
					vc.initDisplayMode(VideoControl.USE_DIRECT_VIDEO, _canvas);
		
					vc.setVisible(true);
					System.out.println("Direct to Canvas");
				} else {
					vc.initDisplayMode(VideoControl.USE_GUI_PRIMITIVE, null);
					//vc.setVisible(true);
					System.out.println("Not Direct to Canvas");
				}
			} catch (IOException e) {
				System.out.println("problem with url " + _url);
		
			} catch (MediaException e) {
				System.out.println("I guess you can't capture");
				fakeIt();
			}
		}
	}
	
	public void setLocationOnCanvas(int _x, int _y){
		if (vc != null){
		vc.setDisplayLocation(_x, _y);
		}
	}


	public void playerUpdate(Player arg0, String arg1, Object arg2) {
		System.out.println("event " + arg1 + " " + arg2);
		try {
			p.start();
		} catch (MediaException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Gets the image from the camera.
	 * 
	 * @return an array of bytes compressed as a png
	 */
	public byte[] grabFrame() {
		if (fakeVideo) {
			return compressedImageBytes;
		}
		compressedImageBytes = null;
		try {
			//imageBytes = vc.getSnapshot( null);
		
			compressedImageBytes = vc.getSnapshot("encoding=" + encoding + "&width=" + imageWidth + "&height=" + imageHeight);

			// imageBytes = vc.getSnapshot("encoding=png&width=" + imageWidth + "&height=" + imageHeight);
		} catch (MediaException e) {
			e.printStackTrace();
		}
		return compressedImageBytes;
	}
	
	public void newFrame(){
		
	
		fakeVideoGraphics.setColor(0);
		fakeVideoGraphics.fillRect(0, 0, imageWidth, imageHeight);
		fakeVideoGraphics.setColor(150);
		if (z >= 100) zDir = -zDir;
		if (z <= 10) zDir = -zDir;
		if (x >= imageWidth) xDir = -xDir;
		if (y >= imageHeight) yDir = -yDir;
		if (x < 0) xDir = -xDir;
		if (y < 0) yDir = -yDir;
		x = x + xDir;
		y = y + yDir;
		z = z + zDir;
		fakeVideoGraphics.setColor(255,155,55);
		fakeVideoGraphics.fillRect(x, y, z, z);

		fakeVideoGraphics.setColor(44,144,244);
		fakeVideoGraphics.fillRect(imageWidth - x, imageHeight -y, 100- z, 100- z);
		
		fakeVideoGraphics.setColor(255);
		fakeVideoGraphics.drawString("Fake Video", 10, 10, 0);

		fakeVideoGraphics.drawString(String.valueOf(System.currentTimeMillis()), 10, 30, 0);
		if(displayCanvas != null){
			displayCanvas.repaint();
		}
	
	}

	/**
	 * Change the resolution that you are grabbing at
	 * 
	 * @param _w
	 * @param _h
	 */
	public void setDimensions(int _w, int _h) {
		imageWidth = _w;
		imageHeight = _h;
		if (vc != null){
		try {
			vc.setDisplaySize(_w, _h);
		} catch (MediaException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		}
	}

	/**
	 * Change an image into an array of ints.
	 * 
	 * @param _image
	 * @return Each int in the array contains RGB and potentially alpha if "unpacked" for one pixel
	 */
	public int[] getPixelArray(Image _image) {
		if (_image == null){
			if (fakeVideo){
				_image = fakeVideoImage;
			}else{
				_image = getImage();
				
			}
		}
	
		uncompressedImageArray = new int[imageWidth * imageHeight];
		_image.getRGB(uncompressedImageArray, 0, imageWidth, 0, 0, imageWidth, imageHeight);
		return uncompressedImageArray;
	}

	/**
	 * Gives you back the bytes of the most recent image as a compressed png.
	 * 
	 * @return
	 */
	public byte[] getCompressedImageBytes() {
		if (fakeVideo) {
			if (fakeVideoPngArray == null){
				System.out.println("Problem finding fakeVideo.png put a 160 x 120 .png file named \"fakeVideo.png\"  in your resource directory or in the root of your project directory");
				return null;
			}else{
			compressedImageBytes = fakeVideoPngArray;
			return compressedImageBytes;
			}
		}
		return compressedImageBytes;
	}

	/**
	 * Gives your the brightness of the a pixel at a given location of the most recently grabbed frame.
	 * 
	 * @param _col
	 * @param _row
	 * @return 0-255;
	 */
	public int[] getBrightness(int _col, int _row) {
		int[] rgb = new int[4];
		int offset = _row * imageWidth + _col;
		rgb[0] = offset;
		int thisPixel = uncompressedImageArray[offset];
		// 0xAARRGGBB
		rgb[1] = (byte) ((thisPixel & 0x000000ff) & 0xff);
		return rgb;
	}

	/**
	 * Gives you back the Red Green and Blue of a pixel at the specified location of the most recent frame.
	 * 
	 * @param _col
	 * @param _row
	 * @return 4 elements, 0 element is offset in the array, 1 is the red, 2, is the green, 3 is the blue
	 */
	public int[] getPixel(int _col, int _row) {
		int[] rgb = new int[4];
		int offset = _row * imageWidth + _col;
		rgb[0] = offset;

		int thisPixel = uncompressedImageArray[offset];
		// 0xAARRGGBB
	//	rgb[1] = (byte) ((thisPixel & 0x000000ff) & 0xff);
		//rgb[2] = (byte) (((thisPixel >>> 8) & 0xff) & 0xff);
		//rgb[3] = (byte) (((thisPixel >>> 16) & 0xff) & 0xff);
		rgb[3] =  ((thisPixel & 0x0000ff) );
		rgb[2] = (((thisPixel & 0x00ff00)  >>> 8));
		rgb[1] =  (((thisPixel & 0xff0000) >>> 16) );
		return rgb;
	}

	/**
	 * Gives you back the Red Green and Blue of a pixel at the specified location in the supplied pixel array.
	 * 
	 * @param _col
	 * @param _row
	 * @return 4 elements, 0 element is offset in the array, 1 is the red, 2, is the green, 3 is the blue
	 */
	public int[] getPixel(int _offset, int[] _imagePixels) {
		int[] rgb = new int[4];

		int thisPixel = _imagePixels[_offset];
		// 0xAARRGGBB
		rgb[3] =  ((thisPixel & 0x0000ff) );
		rgb[2] = (((thisPixel & 0x00ff00)  >>> 8));
		rgb[1] =  (((thisPixel & 0xff0000) >>> 16) );
		return rgb;
	}

	/**
	 * Give the image of the most recent frame.
	 * 
	 * @return
	 */
	public Image getImage() {
		Image myImage = null;

		if (fakeVideo) {
			myImage = fakeVideoImage;
		}else {
			try {
				if (compressedImageBytes == null ) grabFrame();
				myImage = Image.createImage(compressedImageBytes, 0, compressedImageBytes.length);

			} catch (SecurityException e2) {
				System.out.println("Security Issue" + e2);
			} catch (OutOfMemoryError e) {
				System.out.println("Memory Trouble" + e);
			}
	
		}
		return myImage;
		
	}
	
	

	/**
	 * This will make the pixelsource notify you when a new frame is ready. You have to have made your other class a "VideoListener" by implementing "VideoListener." You also have to have put in a "newFrame()" function in the other class.
	 * 
	 * @param _videoListener
	 *            This the class that has the "newFrame" method in it that gets notified.
	 * @param _frameRate
	 * 
	 */
	public void addVideoListener(VideoListener _videoListener, int _frameRate) {

		interval = 1000 / _frameRate;
		listener = _videoListener;
		tickler = new Thread(this);
		tickler.start();
	}
	public void addVideoListener(Canvas _canvas, int _frameRate) {

		interval = 1000 / _frameRate;
		updateCanvas = _canvas;
		tickler = new Thread(this);
		tickler.start();
	}


	public void run() {

		while (true) {
			if (listener != null) {
				listener.newFrame();
			}
			if (updateCanvas != null){
				updateCanvas.repaint();
			}
			try {
				Thread.sleep(interval);
			} catch (InterruptedException e) {
			}

		}

	}

	/**
	 * Give you the latest array of uncompressed image pixels.
	 * 
	 * @return
	 */
	public int[] getUncompressedImageArray() {
		return uncompressedImageArray;
	}

	public void setUncompressedImageArray(int[] imagePixels) {
		this.uncompressedImageArray = imagePixels;
	}

	public int getImageWidth() {
		return imageWidth;
	}

	public int getImageHeight() {
		return imageHeight;
	}

	public String getEncoding() {
		return encoding;
	}

	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}
	
	public boolean requiresManualPainting(){
		return (fakeVideo || displayCanvas == null);
	}
}
